/**
 * SimpleSamplerClient.java
 *
 * Copyright (c) 2013-2016, F(X)yz
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *     * Neither the name of F(X)yz, any associated website, nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL F(X)yz BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */ 

package org.fxyz3d.ExtrasAndTests;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.Duration;
import org.fxyz3d.FXyzSample;
import org.fxyz3d.FXyzSampleBase;
import org.fxyz3d.model.EmptySample;
import org.fxyz3d.model.Project;
import org.fxyz3d.model.SampleTree;
import org.fxyz3d.model.WelcomePage;
import org.fxyz3d.util.SampleScanner;

/**
 *
 * @author Jason Pollastrini aka jdub1581
 */
public class SimpleSamplerClient extends AbstractClientController {

    @FXML
    private BorderPane rootBorderPane;
    @FXML
    private HBox header;
    @FXML
    private StackPane menuPane;
    @FXML
    private VBox leftSide;
    @FXML
    private TextField searchBar;
    @FXML
    private TreeView<FXyzSample> contentTree;
    @FXML
    private HBox statusBar;
    @FXML
    private HBox footer;
    @FXML
    private HBox leftStatusContainer;
    @FXML
    private ProgressBar progressBar;
    @FXML
    private HBox rightStatusContainer;
    @FXML
    private Label rightStatusLabel;
    @FXML
    private StackPane contentPane;
    @FXML
    private VBox centerOverlay;
    @FXML
    private HBox sceneTrackerOverlay;
    @FXML
    private VBox rightSide;
    @FXML
    private VBox descriptionPane;
    @FXML
    private Pane leftSlideTrigger;

    private TreeItem<FXyzSample> root;
    private final Map<String, Project> projectsMap;
    private FXyzSample selectedSample;

    public SimpleSamplerClient(final Stage stage) {
        try {
            FXMLLoader ldr = getUILoader();
            ldr.setController(SimpleSamplerClient.this);
            ldr.setRoot(SimpleSamplerClient.this);
            ldr.load();
        } catch (IOException ex) {
            Logger.getLogger(SimpleSamplerClient.class.getName()).log(Level.SEVERE, null, ex);
        }
        this.stage = stage;
        this.projectsMap = new SampleScanner().discoverSamples();
        buildProjectTree(null);
        initController();
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {

    }

    private void initController() {
        initHeader();
        initLeftPanel();
        initCenterContentPane();
        initCenterContentHeaderOverlay();
        initRightPanel();
        initFooter();
        initialize();
    }

    /**
     * *************************************************************************
     * Path to FXML (extend later for custom layouts (sample:
     * MainSceneController)
     * ************************************************************************
     */
    @Override
    public final FXMLLoader getUILoader() {
        return new FXMLLoader(getClass().getResource(getFXMLPath()));

    }

    @Override
    protected String getFXMLPath() {
        return "Client.fxml";
    }

    /**
     * *************************************************************************
     * Header Setup
     * ************************************************************************
     */
    @Override
    protected void initHeader() {

    }

    /**
     * *************************************************************************
     * LeftPanel Setup
     * ************************************************************************
     */
    @Override
    protected void initLeftPanel() {
        contentTree.setRoot(root);

        searchBar.textProperty().addListener((Observable o) -> {
            buildProjectTree(searchBar.getText());
        });
        contentTree.setShowRoot(false);
        contentTree.getStyleClass().add("samples-tree");
        contentTree.setMinWidth(USE_PREF_SIZE);
        contentTree.setMaxWidth(Double.MAX_VALUE);
        contentTree.setCellFactory(new Callback<TreeView<FXyzSample>, TreeCell<FXyzSample>>() {
            @Override
            public TreeCell<FXyzSample> call(TreeView<FXyzSample> param) {
                return new TreeCell<FXyzSample>() {
                    @Override
                    protected void updateItem(FXyzSample item, boolean empty) {
                        super.updateItem(item, empty);

                        if (empty) {
                            setText("");
                        } else {
                            setText(item.getSampleName());
                        }
                    }
                };
            }
        });
        contentTree.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<TreeItem<FXyzSample>>() {
            @Override
            public void changed(ObservableValue<? extends TreeItem<FXyzSample>> observable, TreeItem<FXyzSample> oldValue, TreeItem<FXyzSample> newSample) {

                if (newSample == null) {
                    return;
                } else if (newSample.getValue() instanceof EmptySample) {
                    FXyzSample selectedSample = newSample.getValue();
                    Project selectedProject = projectsMap.get(selectedSample.getSampleName());
                    System.out.println(selectedProject);
                    if (selectedProject != null) {
                        changeToWelcomePage(selectedProject.getWelcomePage());
                    }
                    return;
                }
                selectedSample = newSample.getValue();
                changeContent();
            }
        });

    }

    /**
     * *************************************************************************
     * ContentSetup Setup
     * ************************************************************************
     */
    @Override
    protected void initCenterContentPane() {
        List<TreeItem<FXyzSample>> projects = contentTree.getRoot().getChildren();
        if (!projects.isEmpty()) {
            TreeItem<FXyzSample> firstProject = projects.get(0);
            contentTree.getSelectionModel().select(firstProject);
        } else {
            changeToWelcomePage(null);
        }
    }

    /**
     * *************************************************************************
     * Content Header Overlay Setup
     * ************************************************************************
     */
    @Override
    protected void initCenterContentHeaderOverlay() {
    }

    /**
     * *************************************************************************
     * Controls Setup
     * ************************************************************************
     */
    @Override
    protected void initRightPanel() {
        
    }

    /**
     * *************************************************************************
     * Footer Setup
     * ************************************************************************
     */
    @Override
    protected void initFooter() {
    }

    /**
     * *************************************************************************
     * Persistent Properties Setup
     * ************************************************************************
     */
    @Override
    protected void loadClientProperties() {
    }

    @Override
    protected void saveClientProperties() {
    }

    /**
     * *************************************************************************
     * ControlsFX FXSampler setup for loading samples
     *
     * /////////////////////////////////////////////////////////////////////////
     */
    /*==========================================================================
     Load all Items into TreeView
     */
    @Override
    protected final void buildProjectTree(String searchText) {
        // rebuild the whole tree (it isn't memory intensive - we only scan
        // classes once at startup)
        root = new TreeItem<>(new EmptySample("FXyz-Sampler"));
        root.setExpanded(true);

        for (String projectName : projectsMap.keySet()) {
            final Project project = projectsMap.get(projectName);
            if (project == null) {
                continue;
            }

            // now work through the project sample tree building the rest
            SampleTree.TreeNode n = project.getSampleTree().getRoot();
            root.getChildren().add(n.createTreeItem());
        }

        // with this newly built and full tree, we filter based on the search text
        if (searchText != null) {
            pruneSampleTree(root, searchText);

            // FIXME weird bug in TreeView I think
            contentTree.setRoot(null);
            contentTree.setRoot(root);
        }

        // and finally we sort the display a little
        sort(root, (o1, o2) -> o1.getValue().getSampleName().compareTo(o2.getValue().getSampleName()));
    }

    private void sort(TreeItem<FXyzSample> node, Comparator<TreeItem<FXyzSample>> comparator) {
        node.getChildren().sort(comparator);
        for (TreeItem<FXyzSample> child : node.getChildren()) {
            sort(child, comparator);
        }
    }

    // true == keep, false == delete
    private boolean pruneSampleTree(TreeItem<FXyzSample> treeItem, String searchText) {
        // we go all the way down to the leaf nodes, and check if they match
        // the search text. If they do, they stay. If they don't, we remove them.
        // As we pop back up we check if the branch nodes still have children,
        // and if not we remove them too
        if (searchText == null) {
            return true;
        }

        if (treeItem.isLeaf()) {
            // check for match. Return true if we match (to keep), and false
            // to delete
            return treeItem.getValue().getSampleName().toUpperCase().contains(searchText.toUpperCase());
        } else {
            // go down the tree...
            List<TreeItem<FXyzSample>> toRemove = new ArrayList<>();

            for (TreeItem<FXyzSample> child : treeItem.getChildren()) {
                boolean keep = pruneSampleTree(child, searchText);
                if (!keep) {
                    toRemove.add(child);
                }
            }

            // remove the unrelated tree items
            treeItem.getChildren().removeAll(toRemove);

            // return true if there are children to this branch, false otherwise
            // (by returning false we say that we should delete this now-empty branch)
            return !treeItem.getChildren().isEmpty();
        }
    }

    public String getSearchString() {
        return searchBar.getText();
    }

    private void changeToWelcomePage(WelcomePage wPage) {
        //change to index above 0 -> 0 will be content header overlay
        contentPane.getChildren().removeIf(index -> contentPane.getChildren().indexOf(index) == 0 && index instanceof StackPane);

        if (null == wPage) {
            wPage = getDefaultWelcomePage();
        }
        contentPane.getChildren().addAll(wPage.getContent());
    }

    private WelcomePage getDefaultWelcomePage() {
        // line 1
        Label welcomeLabel1 = new Label("Welcome to FXSampler!");
        welcomeLabel1.setStyle("-fx-font-size: 2em; -fx-padding: 0 0 0 5;");

        // line 2
        Label welcomeLabel2 = new Label(
                "Explore the available UI controls and other interesting projects "
                + "by clicking on the options to the left.");
        welcomeLabel2.setStyle("-fx-font-size: 1.25em; -fx-padding: 0 0 0 5;");

        WelcomePage wPage = new WelcomePage("Welcome!", new VBox(5, welcomeLabel1, welcomeLabel2));
        return wPage;
    }

    @Override
    protected void changeContent() {
        if (selectedSample == null) {
            return;
        }

        if (!contentPane.getChildren().isEmpty()) {
            contentPane.getChildren().clear();
            rightSide.getChildren().clear();
        }

        updateContent();
    }

    private void updateContent() {
        contentPane.getChildren().addAll(buildSampleTabContent(selectedSample));
        // below add labels / textflow if needed preferably befor controls  
        Node controls = selectedSample.getControlPanel();
        VBox.setVgrow(controls, Priority.ALWAYS);
        rightSide.getChildren().addAll(controls);
        setShowMenuPane(false);
    }

    private Node buildSampleTabContent(FXyzSample sample) {
        return FXyzSampleBase.buildSample(sample, stage);
    }

    public Map<String, Project> getProjectsMap() {
        return projectsMap;
    }

    public FXyzSample getSelectedSample() {
        return selectedSample;
    }

    /*==========================================================================
     Getters and Setters for FXML and SamplerApp Samples
     */
    public BorderPane getRootBorderPane() {
        return rootBorderPane;
    }

    public HBox getHeader() {
        return header;
    }

    public VBox getLeftSide() {
        return leftSide;
    }

    public TextField getSearchBar() {
        return searchBar;
    }

    public TreeView<FXyzSample> getContentTree() {
        return contentTree;
    }

    public HBox getStatusBar() {
        return statusBar;
    }

    public HBox getFooter() {
        return footer;
    }

    public HBox getLeftStatusContainer() {
        return leftStatusContainer;
    }

    public ProgressBar getProgressBar() {
        return progressBar;
    }

    public HBox getRightStatusContainer() {
        return rightStatusContainer;
    }

    public Label getRightStatusLabel() {
        return rightStatusLabel;
    }

    public StackPane getContentPane() {
        return contentPane;
    }

    public VBox getCenterOverlay() {
        return centerOverlay;
    }

    public HBox getSceneTrackerOverlay() {
        return sceneTrackerOverlay;
    }

    public VBox getRightSide() {
        return rightSide;
    }

    //==========================================================================
    //      OPTIONAL Pop-out trays based on Derick Limmerman(?) example
    private final BooleanProperty showMenuPane = new SimpleBooleanProperty(this, "showMenuPane", true);

    public final boolean isShowMenuPane() {
        return showMenuPane.get();
    }

    public final void setShowMenuPane(boolean showMenu) {
        showMenuPane.set(showMenu);
    }

    /**
     * Returns the property used to control the visibility of the menu panel.
     * When the value of this property changes to false then the menu panel will
     * slide out to the left).
     *     
* @return the property used to control the menu panel
     */
    public final BooleanProperty showMenuPaneProperty() {
        return showMenuPane;
    }

    private final BooleanProperty showBottomPane = new SimpleBooleanProperty(this, "showBottomPane", true);

    public final boolean isShowBottomPane() {
        return showBottomPane.get();
    }

    public final void setShowBottomPane(boolean showBottom) {
        showBottomPane.set(showBottom);
    }

    /**
     * Returns the property used to control the visibility of the bottom panel.
     * When the value of this property changes to false then the bottom panel
     * will slide out to the left).
     *     
* @return the property used to control the bottom panel
     */
    public final BooleanProperty showBottomPaneProperty() {
        return showBottomPane;
    }

    public final void initialize() {
        menuPaneLocation.addListener(it -> updateMenuPaneAnchors());
        bottomPaneLocation.addListener(it -> updateBottomPaneAnchors());

        showMenuPaneProperty().addListener(it -> animateMenuPane());
        showBottomPaneProperty().addListener(it -> animateBottomPane());

        menuPane.setOnMouseExited(evt -> setShowMenuPane(false));
        leftSlideTrigger.setOnMouseEntered(evt -> setShowMenuPane(true));
        //menuPane.setOnMouseClicked(evt -> setShowMenuPane(false));

        contentPane.setOnMouseClicked(evt -> {
            //setShowMenuPane(true);
            //setShowBottomPane(true);
        });

        footer.setOnMouseClicked(evt -> setShowBottomPane(false));
    }

    /*
     * The updateMenu/BottomPaneAnchors methods get called whenever the value of
     * menuPaneLocation or bottomPaneLocation changes. Setting anchor pane
     * constraints will automatically trigger a relayout of the anchor pane
     * children.
     */
    private void updateMenuPaneAnchors() {
        setLeftAnchor(menuPane, getMenuPaneLocation());
        setLeftAnchor(contentPane, getMenuPaneLocation() + menuPane.getWidth());
    }

    private void updateBottomPaneAnchors() {
        setBottomAnchor(footer, getBottomPaneLocation());
        setBottomAnchor(contentPane,
                getBottomPaneLocation() + footer.getHeight());
        setBottomAnchor(menuPane,
                getBottomPaneLocation() + footer.getHeight());
    }

    /*
     * Starts the animation for the menu pane.
     */
    private void animateMenuPane() {
        if (isShowMenuPane()) {
            slideMenuPane(0);
        } else {
            slideMenuPane(-leftSide.prefWidth(-1));
        }
    }

    /*
     * Starts the animation for the bottom pane.
     */
    private void animateBottomPane() {
        if (isShowBottomPane()) {
            slideBottomPane(0);
        } else {
            slideBottomPane(-footer.prefHeight(-1));
        }
    }

    /*
     * The animations are using the JavaFX timeline concept. The timeline updates
     * properties. In this case we have to introduce our own properties further
     * below (menuPaneLocation, bottomPaneLocation) because ultimately we need to
     * update layout constraints, which are not properties. So this is a little
     * work-around.
     */
    private void slideMenuPane(double toX) {
        KeyValue keyValue = new KeyValue(menuPaneLocation, toX);
        KeyFrame keyFrame = new KeyFrame(Duration.millis(300), keyValue);
        Timeline timeline = new Timeline(keyFrame);
        timeline.play();
    }

    private void slideBottomPane(double toY) {
        KeyValue keyValue = new KeyValue(bottomPaneLocation, toY);
        KeyFrame keyFrame = new KeyFrame(Duration.millis(300), keyValue);
        Timeline timeline = new Timeline(keyFrame);
        timeline.play();
    }

    private DoubleProperty menuPaneLocation = new SimpleDoubleProperty(this, "menuPaneLocation");

    private double getMenuPaneLocation() {
        return menuPaneLocation.get();
    }

    private DoubleProperty bottomPaneLocation = new SimpleDoubleProperty(this, "bottomPaneLocation");

    private double getBottomPaneLocation() {
        return bottomPaneLocation.get();
    }

}
